﻿// C:\Users\Jorge\Documents\Visual Studio 2010\Projects\C#
//   \LIB\SignalGenLib\GeneratingFn.cs
//
// Class library implementing signal-generation functions.
//
// Programmer:  Jorge L. Orejel
//
// Last update: 02/25/2022

using System;
using System.Collections.Generic;

using System.Windows.Forms;

using Util_Lib;

namespace SignalGenLib
{
   /// <summary>Class to generate digital signals. 
   /// </summary>
   public class GeneratingFn
   {
      public SignalParameters parameters; // Parameters of a signal to be generated.

      public List<TimeMagnitude> sineSignalValues, cosineSignalValues,
                                 squareSignalValues, sawtoothSignalValues, 
                                 triangleSignalValues, whiteNoiseValues;

      public List<double> zeroCrossings; // Signal crossings on the time (X) axis.

      private SignalPlot sineSignal, cosineSignal, squareSignal, 
                         sawtoothSignal, triangleSignal, whiteNoiseSignal;

      /// <summary>Create an instance of a signal-generating function.
      /// </summary>
      /// <param name="_amplitude">Amplitude of the signal in Volts.</param>
      /// <param name="_frequency">Frequency of the signal in Hertz (cycles/second).</param>
      /// <param name="_offset">DC offset to be added to the magnitude of the signal.</param>
      /// <param name="_nSamples">Number of samples to generate for signal-processing.</param>
      /// <param name="_samplingFactor">Factor to multiply by the frequency.
      /// </param>
      public GeneratingFn( double _amplitude, double _frequency, double _offset = 0.0, 
                           int _nSamples = 512, int _samplingFactor = 32 )
      {
         parameters = new SignalParameters( _amplitude, _frequency,                // Mandatory arguments.
                                            _offset, _nSamples, _samplingFactor ); // Optional arguments.

         sineSignalValues = new List<TimeMagnitude>();
         cosineSignalValues = new List<TimeMagnitude>();
         squareSignalValues = new List<TimeMagnitude>();
         sawtoothSignalValues = new List<TimeMagnitude>();
         triangleSignalValues = new List<TimeMagnitude>();
         whiteNoiseValues = new List<TimeMagnitude>();

         sineSignal = new SignalPlot( "sine-signal", SignalShape.sine );
         sineSignal.Text = "Sine signal plot";
         cosineSignal = new SignalPlot( "cosine-signal", SignalShape.cosine );
         cosineSignal.Text = "Cosine signal plot";
         squareSignal = new SignalPlot( "square-signal", SignalShape.square, 
                                      SignalContinuity.discontinuous );
         squareSignal.Text = "Square signal plot";
         sawtoothSignal = new SignalPlot( "sawtooth-signal", SignalShape.sawtooth, 
                                        SignalContinuity.discontinuous );
         sawtoothSignal.Text = "Sawtooth signal plot";
         triangleSignal = new SignalPlot( "triangle-signal", SignalShape.triangle );
         triangleSignal.Text = "Triangle signal plot";
         whiteNoiseSignal = new SignalPlot( "white-noise signal", SignalShape.whiteNoise );
         whiteNoiseSignal.Text = "White noise plot";
      }// GeneratingFn

      /// <summary>Collect the magnitudes from a list of {TimeMagnitude} elements.
      /// </summary>
      /// <param name="tmList">List of (time, magnitude) elements.</param>
      /// <returns>List of magnitudes.
      /// </returns>
      public double[] Magnitudes( List<TimeMagnitude> tmList )
      {
         double[] mags = null;

         if ( tmList != null )
         {
            int n = tmList.Count;

            mags = new double[ n ];
            for ( int i = 0; i < n; ++i )
            {
               mags[ i ] = tmList[ i ].Magnitude;
            }
         }
         return mags;
      }// Magnitudes

      /// <summary>Generate the next sine-signal value.
      /// </summary>
      /// <returns>Current magnitude of the sine signal.
      /// </returns>
      public IEnumerator<double> NextSineSignalValue()
      {
         double angularFreq = 2.0 * Math.PI * parameters.frequency; // w == 2 * pi * f
         double time = 0.0;
         double wt, sinOFwt, magnitude = 0.0;
         TimeMagnitude previousTM = null;

         zeroCrossings = new List<double>();

         while ( true )
         {
            try
            {
               wt = angularFreq * time;
               sinOFwt = Math.Sin( wt );
               magnitude = parameters.offset + ( parameters.amplitude * sinOFwt );
               
               TimeMagnitude tm = new TimeMagnitude( time, magnitude );

               sineSignalValues.Add( tm  );
               CheckZeroCrossing( previousTM, tm );
               previousTM = tm.Copy();
            }
            catch ( Exception exc )
            {
               MessageBox.Show( exc.Message );
               Abort();
            }
            yield return magnitude;

            time += parameters.timeStep;
         }
      }// NextSineSignalValue

      /// <summary>If the {magnitude} of the current {TimeMagnitude} element is near 0.0,
      ///          or if there is a magnitude transition through the time axis from the
      ///          previous {TimeMagnitude} element to the current {TimeMagnitude}
      ///          element, then update the {zeroCrossings} list.
      /// </summary>
      /// <param name="previousTM">Previous {TimeMagnitude} element.</param>
      /// <param name="tm">Current {TimeMagnitude} element.
      /// </param>
      private void CheckZeroCrossing( TimeMagnitude previousTM, TimeMagnitude tm )
      {
         if ( UtilFn.NearZero( tm.Magnitude ) )
         {
            zeroCrossings.Add( tm.Time );
         }
         else if ( previousTM != null && MagnitudeTransition( previousTM, tm ) )
         {
            zeroCrossings.Add( previousTM.Time + ( ( tm.Time - previousTM.Time ) / 2.0 ) );
         }
      }// CheckZeroCrossing

      /// <summary>Determine whether there is a magnitude transition through the time
      ///          axis from the previous {TimeMagnitude} element to the current element.
      /// </summary>
      /// <param name="previousTM">Previous {TimeMagnitude} element.</param>
      /// <param name="currentTM">Current {TimeMagnitude} element.</param>
      /// <returns>{true} if there was a transition, {false} otherwise.
      /// </returns>
      private bool MagnitudeTransition( TimeMagnitude previousTM, TimeMagnitude currentTM )
      {
         return ( previousTM.Magnitude > 0.0 && currentTM.Magnitude < 0.0 )
                ||
                ( previousTM.Magnitude < 0.0 && currentTM.Magnitude > 0.0 );
      }// MagnitudeTransition

      /// <summary>Generate the next cosine-signal value.
      /// </summary>
      /// <returns>Current magnitude of the cosine signal.
      /// </returns>
      public IEnumerator<double> NextCosineSignalValue()
      {
         double angularFreq = 2.0 * Math.PI * parameters.frequency;  // w == 2 * pi * f
         double time = 0.0;
         double wt, cosOFwt, magnitude = 0.0;
         TimeMagnitude previousTM = null;

         zeroCrossings = new List<double>();

         while ( true )
         {
            try
            {
               wt = angularFreq * time;
               cosOFwt = Math.Cos( wt );
               magnitude = parameters.offset + ( parameters.amplitude * cosOFwt );

               TimeMagnitude tm = new TimeMagnitude( time, magnitude );

               cosineSignalValues.Add( tm );
               CheckZeroCrossing( previousTM, tm );
               previousTM = tm.Copy();
            }
            catch ( Exception exc )
            {
               MessageBox.Show( exc.Message );
               Abort();
            }
            yield return magnitude;

            time += parameters.timeStep;
         }
      }// NextCosineSignalValue

      /// <summary>Generate the next square-signal value.
      /// </summary>
      /// <returns>Current magnitude of the square signal.
      /// </returns>
      public IEnumerator<double> NextSquareSignalValue()
      {
         double _amplitude = parameters.amplitude, 
                magnitude = parameters.offset + _amplitude;
         double time = 0.0, t = 0.0;
         bool updateZeroCrossings = magnitude > (double)0.0;

         zeroCrossings = new List<double>();
   
         while ( true )
         {
            try
            {
               TimeMagnitude tm = new TimeMagnitude( time, magnitude );

               squareSignalValues.Add( new TimeMagnitude( time, magnitude ) );
            }
            catch ( Exception exc )
            {
               MessageBox.Show( exc.Message );
               Abort();
            }
            yield return magnitude;

            time += parameters.timeStep;
            t += parameters.timeStep;
            if ( UtilFn.NearZero( t - parameters.halfPeriod ) )
            {
               _amplitude = -_amplitude; // Vertical discontinuity.
               t = 0.0;
               if ( updateZeroCrossings )
               {
                  zeroCrossings.Add( time );
               }
            }
            magnitude = parameters.offset + _amplitude;
         }
      }// NextSquareSignalValue

      /// <summary>Generate the next sawtooth-signal value.
      /// </summary>
      /// <returns>Current magnitude of the sawtooth signal.
      /// </returns>
      public IEnumerator<double> NextSawtoothSignalValue()
      {
         /*
          * A sawtooth signal is generated by repeating a sloped straight
          * line, whose equation is
          * 
          *                y = m * t + b
          *                
          * where {m} is the slope and {b} is the y-axis ordinate.
         */
         double m = 10.0 / parameters.period,
                b = -parameters.amplitude;
         double time = 0.0, t = 0.0;
         double magnitude = 0.0;
         TimeMagnitude previousTM, tm;

         zeroCrossings = new List<double>();

         while ( true )
         {
            previousTM = tm = null;
            try
            {
               magnitude = parameters.offset + ( m * t + b );

               tm = new TimeMagnitude( time, magnitude );

               sawtoothSignalValues.Add( tm );
               CheckZeroCrossing( previousTM, tm );
               previousTM = tm.Copy();
            }
            catch ( Exception exc )
            {
               MessageBox.Show( exc.Message );
               Abort();
            }
            yield return magnitude;

            if ( UtilFn.NearZero( t - parameters.period ) )
            {
               t = 0.0;
               if ( tm.Magnitude > (double)0.0 )
               {
                  zeroCrossings.Add( time ); // Vertical discontinuity.
               }
            }
            time += parameters.timeStep;
            t += parameters.timeStep;
         }
      }// NextSawtoothSignalValue

      /// <summary>Generate the next triangle-signal value.
      /// </summary>
      /// <returns>Current magnitude of the triangle signal.
      /// </returns>
      public IEnumerator<double> NextTriangleSignalValue()
      {
         /*
          * A triangle signal consists of mirrored sloped straight lines,
          * which can be obtained using part of the code for a sawtooth signal.
         */
         double m = 10.0 / parameters.period,
                b = -parameters.amplitude;
         double time = 0.0, t = 0.0;
         double magnitude = 0.0;
         int j = 0;
         TimeMagnitude previousTM, tm;

         tm = previousTM = null;
         bool mirror = false; // No mirroring.

         zeroCrossings = new List<double>();

         while ( true )
         {
            try
            {
               if ( !mirror ) // Line with ascending slope.
               {
                  magnitude = parameters.offset + ( m * t + b );

                  tm = new TimeMagnitude( time, magnitude );

                  triangleSignalValues.Add( tm );
                  ++j;
               }
               else // Mirroring: line with descending slope.
               {
                  if ( j > 0 )
                  {
                     magnitude = triangleSignalValues[ --j ].Magnitude;

                     tm = new TimeMagnitude( time, magnitude );

                     triangleSignalValues.Add( tm );
                  }
               }
            }
            catch ( Exception exc )
            {
               MessageBox.Show( exc.Message );
               Abort();
            }
            CheckZeroCrossing( previousTM, tm );
            previousTM = tm.Copy();
            yield return magnitude;

            if ( UtilFn.NearZero( t - parameters.halfPeriod ) )
            {
               mirror = true; // Start mirroring.
            }
            if ( UtilFn.NearZero( t - parameters.period ) )
            {
               t = 0.0;
               j = 0;
               mirror = false; // Stop mirroring.
            }
            time += parameters.timeStep;
            t += parameters.timeStep;
          }
      }// NextTriangleSignalValue

      /// <summary>Generate the next white-noise value.
      /// </summary>
      /// <returns>Current value of white noise.
      /// </returns>
      public IEnumerator<double> NextWhiteNoiseSignalValue()
      {
         double magnitude = 0.0, time = 0.0, sign;
         Random magRand, signRand;

         magRand = new Random();  // Magnitude random number generator.
         signRand = new Random(); // Random number generator for signal sign.

         zeroCrossings = new List<double>(); // This list will remain empty. 

         while ( true )
         {
            sign = ( signRand.Next( 10 ) > 5 ) ? 1.0 : -1.0;
            magnitude = parameters.offset
                        + sign * ( magRand.NextDouble() * parameters.amplitude );

            whiteNoiseValues.Add( new TimeMagnitude( time, magnitude ) );

            yield return magnitude;

            time += parameters.timeStep;
         }
      }// NextWhiteNoiseSignalValue
      
      /// <summary>Abort execution of the 'user'.
      /// </summary>
      private void Abort()
      {
         if ( System.Windows.Forms.Application.MessageLoop )
         {
            // Windows application
            System.Windows.Forms.Application.Exit();
         }
         else
         {
            // Console application
            System.Environment.Exit( 1 );
         }
      }// Abort

      public void PlotSineSignal()
      {
         sineSignal.Plot( parameters, sineSignalValues );
      }// PlotSineSignal

      public void PlotCosineSignal()
      {
         cosineSignal.Plot( parameters, cosineSignalValues );
      }// PlotSineSignal

      public void PlotSquareSignal()
      {
         squareSignal.Plot( parameters, squareSignalValues, SignalContinuity.discontinuous );
      }// PlotSquareSignal

      public void PlotSawtoothSignal()
      {
         sawtoothSignal.Plot( parameters, sawtoothSignalValues, SignalContinuity.discontinuous );
      }// PlotSawtoothSignal

      /// <summary>
      /// By definition, a triangle signal is discontinuous because the derivative (slope) 
      /// of the function at the peaks does not exist.  However, the discontinuity at
      /// the peaks is not as sharp (i.e. vertical) as in a square signal or a sawtooth
      /// signal.  Hence, the third argument to function {triangleSignal.Plot} is left as
      /// {SignalContinuity.continuous}.
      /// </summary>
      public void PlotTriangleSignal()
      {
         triangleSignal.Plot( parameters, triangleSignalValues );
      }// PlotTriangleSignal

      /// <summary>
      /// By definition, a white noise signal is completely discontinuous because it is
      /// made up by random points on the amplitude vs. time scales.  However, the 
      /// discontinuities are not as sharp (i.e. vertical) as in a square signal or a 
      /// sawtooth signal.  Therefore, the third argument to function {whiteNoiseSignal.Plot} 
      /// is left as {SignalContinuity.continuous}.
      /// </summary>
      public void PlotWhiteNoiseSignal()
      {
         whiteNoiseSignal.Plot( parameters, whiteNoiseValues );
      }// PlotWhiteNoiseSignal
   }// GeneratingFn (class)
}// SignalGenLib (namespace)
